import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

/*
 * PortCommunicator coordinates communication with a single client through
 * an assigned Socket object.  Is run on a thread with each new test.  
 */
class PortCommunicator implements Runnable {
	private Socket server;
	private BufferedReader in;
	private PrintStream out;

	private int playerid;
	private String test;
	public String Solution;
	public int CommandCount;
	private Boolean ready = false;
	public Boolean Running = false;

	// Constructor initializes connection with client and gets ID
	PortCommunicator(Socket server) {
		this.server = server;
		CommandCount = 0;

		String line;

		try {
			// Get input from the client
			InputStreamReader isr = new InputStreamReader(server
					.getInputStream());
			in = new BufferedReader(isr);
			out = new PrintStream(server.getOutputStream());

			// Get initial info - ID
			out.println("ID?");

			line = in.readLine();
			if (line.startsWith("ID=")) {
				playerid = Integer.parseInt(line.split("=")[1].trim());
				System.out.println("Player joined: " + playerid);
			} else {
				System.out.println("\nError reading player ID. Read: " + line
						+ "\nExiting...\n");
				deleteSelf();
			}

			// reply to client
			out.println("ACCEPTED.");
		} catch (NumberFormatException nfe) {
			nfe.printStackTrace();
		} catch (IOException ioe) {
			System.out.println("IOException on socket listen: " + ioe);
			ioe.printStackTrace();
		}
	}

	public void run() {
		Running = true;

		int tempCount = 0;
		String line;
		Solution = "";
		test = RubikServer.Test;

		try {
			// Performing tests
			// Request ready from client
			out.println("READY?");

			// Await response to ready
			line = in.readLine();
			if (!line.contentEquals("READY")) {
				System.out.println("\nInvalid response to 'READY'. Read: "
						+ line + "\nClosing connection...\n");
				deleteSelf();
			}
			ready = true;

			// Await ready flag from server
			while (!RubikServer.Ready) {
				Thread.yield();
			}
			ready = false;

			// Send test to client
			long start = System.currentTimeMillis();
			out.println("SOLVE " + test);

			// Read in commands until "DONE" is read
			line = in.readLine();
			while (!line.contentEquals(playerid + ":DONE")) {
				Solution += line.split(":")[1] + " ";
				if (!line.split(":")[0].contentEquals(Integer
						.toString(playerid))) {
					System.out.println("\nInvalid input from player "
							+ playerid + " : " + line + "\n");
				}
				tempCount++;
				line = in.readLine();
			}
			CommandCount += tempCount;
			long elapsedTimeMillis = System.currentTimeMillis() - start;
			System.out.println("\nPlayer " + playerid + " finished in "
					+ (int) (elapsedTimeMillis / (60 * 1000F)) + " min "
					+ (int) (elapsedTimeMillis / 1000F) % 60 + " seconds "
					+ "using " + tempCount + " turns");
			System.out.println("New total: " + CommandCount + "\n");

			// Verify test results
			if (!verifySolution(Solution, test)) {
				System.out.println("\nIncorrect solution from " + playerid
						+ ". Closing connection...\n");
				out.println(playerid + ":LOSE");
				deleteSelf();
			}
		} catch (NumberFormatException nfe) {
			System.out
					.println("Exception from player " + playerid + ": " + nfe);
		} catch (IOException ioe) {
			System.out
					.println("Exception from player " + playerid + ": " + ioe);
		} finally {
			Running = false;
		}
	}

	public Boolean IsReady() {
		return ready;
	}

	public void SendTimeMessage(int seconds) {
		out.println("TIME:" + seconds);
	}

	private synchronized boolean verifySolution(String solution, String test) {
		Boolean result;
		synchronized (RubikServer.testCreator) {
			result = TestCreator.VerifySolution(solution, test);
		}
		return result;
	}

	public void PrintWinner() {
		System.out.println("Player " + playerid + " won with " + CommandCount
				+ " total turns!");
		out.println(playerid + ":WIN");
	}

	public void PrintLoser() {
		System.out.println("Player " + playerid + " lost with " + CommandCount
				+ " total turns.");
		out.println(playerid + ":LOSE");
	}

	private synchronized void deleteSelf() {
		try {
			server.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		synchronized (RubikServer.players) {
			RubikServer.ConnectionClosed(this);
		}
	}
}